package com.lxm.framework.redis.impl.locker;

import com.lxm.framework.redis.exceptions.RedisLockerOccupiedException;
import com.lxm.framework.redis.inf.LockerSession;
import com.lxm.framework.redis.parts.LockerCore;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.util.concurrent.TimeUnit;

/**
 * @Author: Lys
 * @Date 2023/5/18
 * @Describe
 **/
@Slf4j
public class LockerImpl implements LockerSession {

    private final StringRedisTemplate session;
    private final LockerCore core;

    public LockerImpl(StringRedisTemplate session, LockerCore core) {
        this.session = session;
        this.core = core;
    }

    @Override
    public boolean tryLock() {
        // 检查参数
        checkExpireAndDuration();
        // 计算锁的过期时间
        long expireTime = System.currentTimeMillis() + core.getExpire();
        // 设置锁的key和value
        Boolean result = session.opsForValue().setIfAbsent(core.getFinKey(), String.valueOf(expireTime), core.getExpire(), TimeUnit.MILLISECONDS);
        if (result != null && result) {
            // 设置成功，表示获取锁成功
            return true;
        }
        // 设置失败，获取锁的value，判断是否超时
        String currentValue = session.opsForValue().get(core.getFinKey());
        if (currentValue != null && Long.parseLong(currentValue) < System.currentTimeMillis()) {
            // 超时，重新设值
            result = session.opsForValue().setIfPresent(core.getFinKey(), String.valueOf(expireTime), core.getExpire(), TimeUnit.MILLISECONDS);
            return null != result && result;
        }
        // 其他情况，表示获取锁失败
        return false;
    }

    @Override
    public void releaseLock() {
        // 获取锁的value，判断是否是当前线程设置的
        String currentValue = session.opsForValue().get(core.getFinKey());
        if (currentValue != null) {
            // 使用del命令删除key，释放锁
            session.delete(core.getFinKey());
        }
    }


    @Override
    public void accessResource(boolean hold) throws RedisLockerOccupiedException {
        // 尝试获取锁
        boolean fetch = tryLock();
        long holding = 0L;
        boolean throwIt;
        for (throwIt = false; !fetch; fetch = this.tryLock()) {
            // 如果不等，就直接抛错
            if (!hold) {
                throwIt = true;
                break;
            }
            // 没获取到，就等
            try {
                TimeUnit.MILLISECONDS.sleep(core.getDuration());
                holding += core.getDuration();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            if (holding > core.getExpire()) {
                throwIt = true;
                break;
            }
        }
        if (throwIt) {
            throw new RedisLockerOccupiedException();
        }
    }

    @Override
    public void accessResourceThenRelease(Runnable task) throws RedisLockerOccupiedException {
        accessResource();
        task.run();
        releaseLock();
    }

    /**
     * duration 不能小于 expire
     * 若小于，则令其相等
     */
    private void checkExpireAndDuration() {
        final Long expire = core.getExpire();
        final Long duration = core.getDuration();
        final TimeUnit expireUnit = core.getExpireUnit();
        final TimeUnit durationUnit = core.getDurationUnit();
        if (expireUnit.toMillis(expire) < durationUnit.toMillis(duration)) {
            //throw new RedisLockerInvalidParamsException();
            core.setDuration(expire).setDurationUnit(expireUnit);
        }
    }
}
